#include "virginian.h"
#include "test/test.h"
#include "test/queries2.h"
#include "gsl/gsl_rng.h"
#include "gsl/gsl_randist.h"

namespace {

#define DBNAME "growthdb"
#define INCREMENT 500
#define CEILING 3500

class CartesianGrowthTest : public VirginianTest {
protected:

	virginian *v;
	gsl_rng *ran;
	int rows_added;

	CartesianGrowthTest()
	{
		unlink(DBNAME);
		v = (virginian*)malloc(sizeof(virginian));

		rows_added = 0;
		virg_init(v);
		virg_db_create(v, DBNAME);
		virg_table_create(v, "test", VIRG_INT);
		virg_table_addcolumn(v, 0, "uniformi", VIRG_INT);
		virg_table_addcolumn(v, 0, "normali5", VIRG_INT);
		virg_table_addcolumn(v, 0, "normali20", VIRG_INT);
		virg_table_addcolumn(v, 0, "uniformf", VIRG_FLOAT);
		virg_table_addcolumn(v, 0, "normalf5", VIRG_FLOAT);
		virg_table_addcolumn(v, 0, "normalf20", VIRG_FLOAT);

		virg_table_create(v, "test1", VIRG_INT);
		virg_table_addcolumn(v, 1, "uniformi", VIRG_INT);
		virg_table_addcolumn(v, 1, "normali5", VIRG_INT);
		virg_table_addcolumn(v, 1, "normali20", VIRG_INT);
		virg_table_addcolumn(v, 1, "uniformf", VIRG_FLOAT);
		virg_table_addcolumn(v, 1, "normalf5", VIRG_FLOAT);
		virg_table_addcolumn(v, 1, "normalf20", VIRG_FLOAT);

		const gsl_rng_type *type;
		type = gsl_rng_ranlxd2;
		ran = gsl_rng_alloc(type);
		gsl_rng_set(ran, time(0));


	}

	void AddRows(int rows)
	{
		int i;
		void *buff = malloc(4 * 6);
		int *buff_i = (int*)buff;
		float *buff_f = (float*)buff;

		for(i = 0; i < rows; i++) {
			buff_i[0] = (int)gsl_ran_flat(ran, -100, 100);
			buff_i[1] = (int)gsl_ran_gaussian(ran, 5);
			buff_i[2] = (int)gsl_ran_gaussian(ran, 20);
			buff_f[3] = (float)gsl_ran_flat(ran, -100, 100);
			buff_f[4] = (float)gsl_ran_gaussian(ran, 5);
			buff_f[5] = (float)gsl_ran_gaussian(ran, 20);
			virg_table_insert(v, 0, (char*)&i, (char*)buff, NULL);
			virg_table_insert(v, 1, (char*)&i, (char*)buff, NULL);

		}

		free(buff);
		rows_added += rows;

		printf("%i,", rows_added);
		fflush(stdout);
	}

	void AddZeroRows(int rows)
	{
		int i;
		void *buff = malloc(4 * 6);
		int *buff_i = (int*)buff;
		float *buff_f = (float*)buff;

		for(i = 0; i < rows; i++) {
			buff_i[0] = (int)0;
			buff_i[1] = (int)0;
			buff_i[2] = (int)0;
			buff_f[3] = (float)0.0;
			buff_f[4] = (float)0.0;
			buff_f[5] = (float)0.0;
			virg_table_insert(v, 0, (char*)&i, (char*)buff, NULL);
			virg_table_insert(v, 1, (char*)&i, (char*)buff, NULL);

		}

		free(buff);
		rows_added += rows;

		printf("%i,", rows_added);
		fflush(stdout);
	}

	void AddOneRows(int rows)
	{
		int i;
		void *buff = malloc(4 * 6);
		int *buff_i = (int*)buff;
		float *buff_f = (float*)buff;

		for(i = 0; i < rows; i++) {
			buff_i[0] = (int)1;
			buff_i[1] = (int)1;
			buff_i[2] = (int)1;
			buff_f[3] = (float)1;
			buff_f[4] = (float)1;
			buff_f[5] = (float)1;
			virg_table_insert(v, 0, (char*)&i, (char*)buff, NULL);
			virg_table_insert(v, 1, (char*)&i, (char*)buff, NULL);

		}

		free(buff);
		rows_added += rows;

		printf("%i,", rows_added);
		fflush(stdout);
	}

	void AddIncRows(int rows)
	{
		int i;
		void *buff = malloc(4 * 6);
		int *buff_i = (int*)buff;
		float *buff_f = (float*)buff;

		for(i = 1; i <= rows; i++) {
			buff_i[0] = (int)i;
			buff_i[1] = (int)i;
			buff_i[2] = (int)i;
			buff_f[3] = (float)i;
			buff_f[4] = (float)i;
			buff_f[5] = (float)i;
			virg_table_insert(v, 0, (char*)&i, (char*)buff, NULL);
			virg_table_insert(v, 1, (char*)&i, (char*)buff, NULL);

		}

		free(buff);
		rows_added += rows;

		printf("%i,", rows_added);
		fflush(stdout);
	}

	virtual ~CartesianGrowthTest()
	{
		virg_db_close(v);
		virg_close(v);
		cudaThreadExit();
		gsl_rng_free(ran);
		unlink(DBNAME);
	}

	double RunSuite(int cached)
	{
		double x = 0;

		for(int i = 0; i < query_size; i++) {
			virg_vm *vm = virg_vm_init();
			virg_sql(v, queries[i], vm);
			virg_table_loadmem(v, 0);
			//virg_print_stmt(vm);

			if(!cached)
				virg_timer_start();

			virg_vm_execute(v, vm);

			if(!cached)
				x += virg_timer_stop();
			else
				x += vm->timing2;

			virg_vm_freeresults(v, vm);
		}

		return x / query_size;
	}

	double RunQuery(char* query)
	{
		double x = 0;


		virg_vm *vm = virg_vm_init();
		virg_sql(v, query, vm);
		virg_table_loadmem(v, 0);
		//virg_print_stmt(vm);


		virg_timer_start();

		virg_vm_execute(v, vm);

		x += virg_timer_stop();


		virg_vm_freeresults(v, vm);


		return x;
	}
};



TEST_F(CartesianGrowthTest, Memory) {
	AddZeroRows(INCREMENT);
	double b, c, d;

	FILE *f = fopen("cartesian_growth_memory.dat", "w+");
	fprintf(f, "\"Rows\"\t\"CPU\"\t\"GPU\"\n");

	while(rows_added <= CEILING) {
		v->use_multi	= 0;
		v->use_gpu		= 0;
		v->use_mmap		= 0;
		b = RunQuery("select test.id, test1.uniformi from test,test1");

		v->use_multi	= 0;
		v->use_gpu		= 1;
		v->use_mmap		= 1;
		c = RunQuery("select test.id, test1.uniformi from test,test1");


		fprintf(f, "%i\t%'.5f\t%'.5f\n", rows_added, b, c);

		AddZeroRows(INCREMENT);
		cudaDeviceSynchronize();
	}

	fclose(f);
	printf("\n");
}

TEST_F(CartesianGrowthTest, Speed) {
	AddOneRows(INCREMENT);
	double b, c, d;

	FILE *f = fopen("cartesian_growth_speed.dat", "w+");
	fprintf(f, "\"Rows\"\t\"CPU\"\t\"GPU\"\n");

	while(rows_added <= CEILING) {
		v->use_multi	= 0;
		v->use_gpu		= 0;
		v->use_mmap		= 0;
		b = RunQuery("select test.id, test1.id AS id2, test.uniformi, test1.uniformi from test,test1 where test.id = test1.id and test1.uniformi = 1");

		v->use_multi	= 0;
		v->use_gpu		= 1;
		v->use_mmap		= 1;
		c = RunQuery("select test.id, test1.id AS id2, test.uniformi, test1.uniformi from test,test1 where test.id = test1.id and test1.uniformi = 1");


		fprintf(f, "%i\t%'.5f\t%'.5f\n", rows_added, b, c);

		AddZeroRows(INCREMENT);
		cudaDeviceSynchronize();
	}

	fclose(f);
	printf("\n");
}

TEST_F(CartesianGrowthTest, Proportion) {
	AddIncRows(3000);
	double b, c, d;

	FILE *f = fopen("cartesian_growth_proportion.dat", "w+");
	fprintf(f, "\"Percent\"\t\"Rows\"\t\"CPU\"\t\"GPU\"\n");

	// ------------------------------------------
	// 10%
	// ------------------------------------------
	v->use_multi	= 0;
	v->use_gpu		= 0;
	v->use_mmap		= 0;
	b = RunQuery("select test.uniformi, test1.uniformi from test,test1 where ((test.uniformi + 1) * 1000 + (test1.uniformi + 1)) - 1000 <= 1000000");

	v->use_multi	= 0;
	v->use_gpu		= 1;
	v->use_mmap		= 1;
	c = RunQuery("select test.uniformi, test1.uniformi from test,test1 where ((test.uniformi + 1) * 1000 + (test1.uniformi + 1)) - 1000 <= 1000000");

	fprintf(f, "%f\t%i\t%'.5f\t%'.5f\n", .1, 100000, b, c);
	cudaDeviceSynchronize();

	// ------------------------------------------
	// 20%
	// ------------------------------------------
	v->use_multi	= 0;
	v->use_gpu		= 0;
	v->use_mmap		= 0;
	b = RunQuery("select test.uniformi, test1.uniformi from test,test1 where ((test.uniformi + 1) * 1000 + (test1.uniformi + 1)) - 1000 <= 2000000");

	v->use_multi	= 0;
	v->use_gpu		= 1;
	v->use_mmap		= 1;
	c = RunQuery("select test.uniformi, test1.uniformi from test,test1 where ((test.uniformi + 1) * 1000 + (test1.uniformi + 1)) - 1000 <= 2000000");

	fprintf(f, "%f\t%i\t%'.5f\t%'.5f\n", .2, 200000, b, c);
	cudaDeviceSynchronize();

	// ------------------------------------------
	// 30%
	// ------------------------------------------
	v->use_multi	= 0;
	v->use_gpu		= 0;
	v->use_mmap		= 0;
	b = RunQuery("select test.uniformi, test1.uniformi from test,test1 where ((test.uniformi + 1) * 1000 + (test1.uniformi + 1)) - 1000 <= 3000000");

	v->use_multi	= 0;
	v->use_gpu		= 1;
	v->use_mmap		= 1;
	c = RunQuery("select test.uniformi, test1.uniformi from test,test1 where ((test.uniformi + 1) * 1000 + (test1.uniformi + 1)) - 1000 <= 3000000");

	fprintf(f, "%f\t%i\t%'.5f\t%'.5f\n", .3, 300000, b, c);
	cudaDeviceSynchronize();

	// ------------------------------------------
	// 40%
	// ------------------------------------------
	v->use_multi	= 0;
	v->use_gpu		= 0;
	v->use_mmap		= 0;
	b = RunQuery("select test.uniformi, test1.uniformi from test,test1 where ((test.uniformi + 1) * 1000 + (test1.uniformi + 1)) - 1000 <= 4000000");

	v->use_multi	= 0;
	v->use_gpu		= 1;
	v->use_mmap		= 1;
	c = RunQuery("select test.uniformi, test1.uniformi from test,test1 where ((test.uniformi + 1) * 1000 + (test1.uniformi + 1)) - 1000 <= 4000000");

	fprintf(f, "%f\t%i\t%'.5f\t%'.5f\n", .4, 400000, b, c);
	cudaDeviceSynchronize();

	// ------------------------------------------
	// 50%
	// ------------------------------------------
	v->use_multi	= 0;
	v->use_gpu		= 0;
	v->use_mmap		= 0;
	b = RunQuery("select test.uniformi, test1.uniformi from test,test1 where ((test.uniformi + 1) * 1000 + (test1.uniformi + 1)) - 1000 <= 5000000");

	v->use_multi	= 0;
	v->use_gpu		= 1;
	v->use_mmap		= 1;
	c = RunQuery("select test.uniformi, test1.uniformi from test,test1 where ((test.uniformi + 1) * 1000 + (test1.uniformi + 1)) - 1000 <= 5000000");

	fprintf(f, "%f\t%i\t%'.5f\t%'.5f\n", .5, 500000, b, c);
	cudaDeviceSynchronize();

	// ------------------------------------------
	// 60%
	// ------------------------------------------
	v->use_multi	= 0;
	v->use_gpu		= 0;
	v->use_mmap		= 0;
	b = RunQuery("select test.uniformi, test1.uniformi from test,test1 where ((test.uniformi + 1) * 1000 + (test1.uniformi + 1)) - 1000 <= 6000000");

	v->use_multi	= 0;
	v->use_gpu		= 1;
	v->use_mmap		= 1;
	c = RunQuery("select test.uniformi, test1.uniformi from test,test1 where ((test.uniformi + 1) * 1000 + (test1.uniformi + 1)) - 1000 <= 6000000");

	fprintf(f, "%f\t%i\t%'.5f\t%'.5f\n", .6, 600000, b, c);
	cudaDeviceSynchronize();

	// ------------------------------------------
	// 70%
	// ------------------------------------------
	v->use_multi	= 0;
	v->use_gpu		= 0;
	v->use_mmap		= 0;
	b = RunQuery("select test.uniformi, test1.uniformi from test,test1 where ((test.uniformi + 1) * 1000 + (test1.uniformi + 1)) - 1000 <= 7000000");

	v->use_multi	= 0;
	v->use_gpu		= 1;
	v->use_mmap		= 1;
	c = RunQuery("select test.uniformi, test1.uniformi from test,test1 where ((test.uniformi + 1) * 1000 + (test1.uniformi + 1)) - 1000 <= 7000000");

	fprintf(f, "%f\t%i\t%'.5f\t%'.5f\n", .7, 700000, b, c);
	cudaDeviceSynchronize();

	// ------------------------------------------
	// 80%
	// ------------------------------------------
	v->use_multi	= 0;
	v->use_gpu		= 0;
	v->use_mmap		= 0;
	b = RunQuery("select test.uniformi, test1.uniformi from test,test1 where ((test.uniformi + 1) * 1000 + (test1.uniformi + 1)) - 1000 <= 8000000");

	v->use_multi	= 0;
	v->use_gpu		= 1;
	v->use_mmap		= 1;
	c = RunQuery("select test.uniformi, test1.uniformi from test,test1 where ((test.uniformi + 1) * 1000 + (test1.uniformi + 1)) - 1000 <= 8000000");

	fprintf(f, "%f\t%i\t%'.5f\t%'.5f\n", .8, 800000, b, c);
	cudaDeviceSynchronize();

	// ------------------------------------------
	// 90%
	// ------------------------------------------
	v->use_multi	= 0;
	v->use_gpu		= 0;
	v->use_mmap		= 0;
	b = RunQuery("select test.uniformi, test1.uniformi from test,test1 where ((test.uniformi + 1) * 1000 + (test1.uniformi + 1)) - 1000 <= 9000000");

	v->use_multi	= 0;
	v->use_gpu		= 1;
	v->use_mmap		= 1;
	c = RunQuery("select test.uniformi, test1.uniformi from test,test1 where ((test.uniformi + 1) * 1000 + (test1.uniformi + 1)) - 1000 <= 9000000");

	fprintf(f, "%f\t%i\t%'.5f\t%'.5f\n", .9, 900000, b, c);
	cudaDeviceSynchronize();

	// ------------------------------------------
	// 100%
	// ------------------------------------------
	v->use_multi	= 0;
	v->use_gpu		= 0;
	v->use_mmap		= 0;
	b = RunQuery("select test.uniformi, test1.uniformi from test,test1 where ((test.uniformi + 1) * 1000 + (test1.uniformi + 1)) - 1000 <= 10000000");

	v->use_multi	= 0;
	v->use_gpu		= 1;
	v->use_mmap		= 1;
	c = RunQuery("select test.uniformi, test1.uniformi from test,test1 where ((test.uniformi + 1) * 1000 + (test1.uniformi + 1)) - 1000 <= 10000000");

	fprintf(f, "%f\t%i\t%'.5f\t%'.5f\n", 1.0, 1000000, b, c);
	cudaDeviceSynchronize();

	fclose(f);
	printf("\n");
}

TEST_F(CartesianGrowthTest, Run) {
	AddRows(INCREMENT);

	double b, c, d;

	FILE *f = fopen("cartesian_growth.dat", "w+");
	fprintf(f, "\"Rows\"\t\"CPU\"\t\"GPU\"\n");

	while(rows_added <= CEILING) {
		v->use_multi	= 0;
		v->use_gpu		= 0;
		v->use_mmap		= 0;
		b = RunSuite(0);

		v->use_multi	= 0;
		v->use_gpu		= 1;
		v->use_mmap		= 1;
		c = RunSuite(0);


		fprintf(f, "%i\t%'.5f\t%'.5f\n", rows_added, b, c);

		AddRows(INCREMENT);
		cudaDeviceSynchronize();
	}

	fclose(f);
	printf("\n");
}

}

